<?php
declare (strict_types=1);

namespace app\common\builder;

use app\common\builder\form\traits\ParseItemElem;
use ArrayAccess;
use Countable;
use think\helper\Str;

class Form implements ArrayAccess, Countable
{
    use \app\common\builder\form\traits\ParseItem;
    use \app\common\builder\form\traits\ParseItemElem;

    protected $app;

    protected $request;

    /**
     * @var 模板实例对象
     */
    protected $template;

    /**
     * @var 模型
     */
    protected $model;

    /**
     * 表单项集合
     * @var array
     */
    protected $items = [];

    /**
     * 默认注册到不同类型的标签属性
     * @var array
     */
    protected $itemForAttrs = [
        'text' => [
            'class' => 'layui-input'
        ],
        'password' => [
            'class' => 'layui-input'
        ],
        'number' => [
            'class' => 'layui-input'
        ],
        'h5url' => [
            'class' => 'layui-input'
        ],
        'h5tel' => [
            'class' => 'layui-input'
        ],
        'h5email' => [
            'class' => 'layui-input'
        ],
        'date' => [
            'class' => 'layui-input'
        ],
        'datetime' => [
            'class' => 'layui-input'
        ],
        'month' => [
            'class' => 'layui-input'
        ],
        'time' => [
            'class' => 'layui-input'
        ],
        'year' => [
            'class' => 'layui-input'
        ],
        'color' => [
            'class' => 'layui-input'
        ],
        'textarea' => [
            'class' => 'layui-textarea'
        ]
    ];

    /**
     * 表单值
     * @var array
     */
    protected $data = [];

    /**
     * Form constructor.
     * @param array $data 表单的初始值默认值
     * @param null $model 当前表单对应的模型
     */
    public function __construct($data = [], $model = null)
    {
        $this->app =  app();
        $this->request = $this->app->request;

        $this->template = new \think\Template();
        $this->template->config([
            'view_path'	    =>	'',
            'cache_path'	=>	$this->app->getRuntimePath() . 'form' . DIRECTORY_SEPARATOR,
            'view_suffix'   =>	'',
        ]);

        // 模型 ...


        // 数据
        if ($this->request->isPost() || $this->request->isPut()) {
            $this->data = $this->request->post();
        } else {
            $this->data = $data;
        }
    }

    /**
     * 添加表单字段
     * @param string $field 字段名
     * @param string $elem 表单类型
     * @param array $options
     * @return $this
     * @throws \Exception
     */
    public function addFormItem(string $field, string $elem = 'text', array $options = [])
    {
        // 每个表单应该有哪些属性？ 基础属性 大多数情况是添加表单字段的时候 设置的
        // elem  ：表单类型  text password radio  ...
        // field ：字段
        // label ：中文标题
        // tip   ： 提示
        // isRequired ：必填星星
        // options : 选项
        // attrs : 标签上属性 数组
             // id  class  value  name ...
        // before : 自定义前置回调
        // after  ： 自定义后置回调
        // --------------------
        // 大多数是由程序去处理的属性，这些属性是需要，但不需要设置
        // labelTag： label标签
        // fetch : 表单项模板  默认在 common/builder/form/items/xxxx.html
        // html  : 表单渲染出来的html结构
        // ....
        $field = trim($field);
        $elem = trim($elem);
        if (empty($field)) {
            throw  new \Exception('表单字段名不能为空');
        }
        if (array_key_exists($field, $this->items)) {
            throw  new \Exception('该字段' . $field .'已经存在');
        }
        $this->items[$field] = array_merge(['field' => $field, 'elem' => $elem], $options);
        return $this;
    }

    /**
     * 修改表单字段
     * @param string $field 字段
     * @param $attr 如果是字符串就标识修改具体属性名  如果是数组就是批量修改
     * @param null $value 修改的值
     * @return $this
     * @throws \Exception
     */
    public function updateFormItem(string $field, $attr, $value = null)
    {
        $field = trim($field);
        if (empty($field)) {
            throw  new \Exception('需要修改的表单字段名不能为空');
        }
        if (!array_key_exists($field, $this->items)) {
            throw  new \Exception('该字段' . $field .'不存在');
        }

        if (is_array($attr)) {
            $this->items[$field] = array_merge($this->items[$field], $attr);
            return $this;
        }
        $this->items[$field][$attr] = $value;
        return $this;
    }

    /**
     * 删除表单字段
     * @param string $field 需要删除的字段名
     * @return $this
     */
    public function deleteFormItem(string $field)
    {
        $field = trim($field);
        if (array_key_exists($field, $this->items)) {
            unset($this->items[$field]);
        }
        return $this;
    }

    /**
     * 获取表单字段
     * $field 不填 获取所有字段信息；
     * $field 有 $attr 不填 获取指定字段所有信息
     * $field 有 $attr 有  获取指定字段的指定信息
     * @param string|null $field
     * @param string|null $attr
     * @return array|mixed|string
     * @throws \Exception
     */
    public function getFormItem(string $field = null, string $attr = null)
    {
        if (is_null($field)) {
            return $this->items;
        }
        $field = trim($field);
        if (!array_key_exists($field, $this->items)) {
            throw  new \Exception('你要获取的字段' . $field .'不存在');
        }
        if (is_null($attr)) {
            return $this->items[$field];
        }
        return isset($this->items[$field][$attr]) ? $this->items[$field][$attr] : '';
    }


    /**
     * 判断指定表单字段是否存在
     * @param string $field
     * @return bool
     */
    public function formItemExists(string $field)
    {
        $field = trim($field);
        return array_key_exists($field, $this->items);
    }

    /**
     * 给表单字符赋值
     * @param $field 数组 批量赋值  字符串 给指定字段赋值
     * @param null $value
     * @return $this
     */
    public function setItemValue($field, $value = null)
    {
        /* // 如果没有设置表单字段，不允许给改字段赋值
        if (array_key_exists($field, $this->items)) {
            return $this;
        }
        */
        if (is_array($field)) {
            foreach ($field as $f => $v) {
                $this->setItemValue($f, $v);
            }
            return $this;
        }
        if (($this->request->isPost() || $this->request->isPut()) && isset($this->data[$field])) {

            $value = $this->data[$field];
        }
        $this->data[$field] = $value;
        return $this;
    }

    /**
     * 获取当前表单字段的值
     * @param null $field 不传 就获取到全部 字符串 获取指定字段的值
     * @return array|mixed|string
     */
    public function getItemValue($field = null)
    {
        if (!isset($field)) {
            return $this->data;
        }
        return $this->data[$field] ?? '';
    }

    /**
     * 分别处理解析每个表单字段
     */
    public function parseItems()
    {
        foreach ($this->items as $field => &$item) {
            $item = $this->parseItem($item);
        }
    }

    /**
     * @param $item
     * @param bool $force 是否强制解析
     * @return mixed
     */
    public function parseItem($item, $force = false)
    {
        if (!empty($item['is_parse']) && !$force) {
            return $item;
        }
        // 每个字段的自定义前置回调
        if (isset($item['before']) && is_callable($item['before'])) {
            $item = call_user_func_array($item['before'], [$item]);
        }

        // 表单类型前置回调
        if (!empty($item['elem'])) {
            $callbackFunc = "before" . Str::studly($item['elem']) . "Parse";
            if (method_exists($this, $callbackFunc)) {
                $item = call_user_func_array([$this, $callbackFunc], [$item]);
            }
        }

        // 表单属性回调
        foreach ($this->parseAttrsList as $attr)
        {
            $callbackFunc = 'parse' . Str::studly($attr) . 'Attr';
            if (method_exists($this, $callbackFunc)) {
                $item = call_user_func_array([$this, $callbackFunc], [$item]);
            }
        }

        // 每个字段的自定义后置回调
        if (isset($item['after']) && is_callable($item['after'])) {
            $item = call_user_func_array($item['after'], [$item]);
        }

        // 表单类型后置回调
        if (!empty($item['elem'])) {
            $callbackFunc = "after" . Str::studly($item['elem']) . "Parse";
            if (method_exists($this, $callbackFunc)) {
                $item = call_user_func_array([$this, $callbackFunc], [$item]);
            }
        }
        $item['html'] = $this->parseHtmlAttr($item);
        $item['is_parse'] = true;
        return $item;
    }

    public function fetch($template, $data = [], $trim = true)
    {
        ob_start();
        $default = [
            'root' => $this->request->realRoot(),
            'isMobile' => $this->request->isMobile(),
            'approot' => $this->request->root()
        ];
        $this->template->fetch($template, array_merge(['data' => $data, 'values' => $this->data, $default]));
        $content = ob_get_contents();
        if ($trim) {
            $content = preg_replace_callback("/<[^<]*?>/is", function ($matched) {
                return preg_replace("/\s*" . PHP_EOL . "\s*/is", " ", $matched[0]);
            }, $content);
        }
        ob_end_clean();
        return $content;
    }

    // 表单最终布局输出 -- 给最终表单做很多自定义模板
    protected function fetchLayout($layout = 'default')
    {
        // 默认在当前应用下的builder/form/layout找布局模板文件
        $layout_templte  = app()->getAppPath() . 'view' .
            DIRECTORY_SEPARATOR . 'builder' .
            DIRECTORY_SEPARATOR . 'form' .
            DIRECTORY_SEPARATOR . 'layout' .
            DIRECTORY_SEPARATOR . $layout . '.html';

        // 默认布局模板
        if (!is_file($layout_templte)) {
            $layout_templte = app()->getBasePath() . 'common' .
                DIRECTORY_SEPARATOR . 'builder' .
                DIRECTORY_SEPARATOR . 'form' .
                DIRECTORY_SEPARATOR . 'layout' .
                DIRECTORY_SEPARATOR . $layout . '.html';
        }
        if (!is_file($layout_templte)) {
            throw  new \Exception("表单模板不存在:{$layout_templte}");
        }
        return $this->fetch($layout_templte, $this);
    }

    // 当把当前类的实例对象，当成函数来调用，自动执行 $form('table') 指定布局模板
    public function __invoke($layout = 'default')
    {
        return $this->fetchLayout($layout);
    }

    // 当把当前类的实例对象，当成字符串输出的时候，自动执行
    public function __toString()
    {
        // 即将渲染布局 把每个表单项所有数据都要准备好
        // 处理每个表单字段的数据
        $this->parseItems();
        //pr($this->data);
        //pr($this->items);


        // 渲染最终表单布局
        return $this->fetchLayout('default');
    }

    // ArrayAccess
    #[\ReturnTypeWillChange]
    public function offsetExists($offset) : bool
    {
        return $this->formItemExists($offset);
    }

    #[\ReturnTypeWillChange]
    public function offsetGet($offset)
    {
        return $this->getFormItem($offset);
    }

    #[\ReturnTypeWillChange]
    public function offsetSet($offset, $value)
    {
        if (array_key_exists($offset, $this->items)) {
            $this->updateFormItem($offset, $value);
        } else {
            $this->addFormItem($offset, isset($value['elem']) ? $value['elem'] : 'text', $value);
        }
    }

    #[\ReturnTypeWillChange]
    public function offsetUnset($offset)
    {
        $this->deleteFormItem($offset);
    }

    //Countable
    public function count(): int
    {
        return count($this->items);
    }
}